package com.ruoyi.oauth.sso.service;

import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oauth.domain.OauthClient;
import com.ruoyi.oauth.domain.OauthLog;
import com.ruoyi.oauth.domain.OauthToken;
import com.ruoyi.oauth.service.IOauthClientService;
import com.ruoyi.oauth.service.IOauthLogService;
import com.ruoyi.oauth.service.IOauthTokenService;
import com.ruoyi.oauth.sso.vo.OAuthCode;
import com.ruoyi.oauth.sso.vo.OAuthRequest;
import com.ruoyi.oauth.utils.OAuthUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

@Service
public class OauthServiceImpl implements IOauthService {
    private static final Logger log = LoggerFactory.getLogger(OauthServiceImpl.class);
    @Autowired
    private IOauthClientService oauthClientService;
    @Autowired
    private IOauthTokenService oauthTokenService;
    @Autowired
    private IOauthLogService oauthLogService;
    @Autowired
    private OauthLoginService oauthLoginService;
    @Autowired
    private RedisCache redisCache;


    /**
     * 验证客户端是否存在
     * @param model
     * @param clientId
     * @return
     */
    @Override
    public OauthClient checkClient(Model model, String clientId){
        OauthClient oauthClient = checkClient(clientId, null);
        if(oauthClient != null){
            model.addAttribute("client", oauthClient);
        }
        return oauthClient;
    }

    /**
     * 验证客户端是否有效
     * @param clientId
     * @param clientSecret
     * @return
     */
    public OauthClient checkClient(String clientId, String clientSecret){
        if(StringUtils.isEmpty(clientId)){
            throw new ServiceException("错误:校验客户端ID不能为空!");
        }
        OauthClient oauthClient = oauthClientService.selectOauthClientByClientId(clientId);
        if(oauthClient == null){
            throw new ServiceException("错误:校验客户端ID失败!");
        }else if(StringUtils.isEmpty(clientSecret)){
            return oauthClient;
        }else if(clientSecret.equals(oauthClient.getClientSecret())){
            return oauthClient;
        }else{
            throw new ServiceException("错误:校验客户端密钥失败!");
        }
    }

    /**
     * 生成临时验证码
     * @param request
     * @return
     */
    public OAuthCode buildCode(OAuthRequest request){
        OauthClient client = checkClient(request.getClientId(), null);
        if(StringUtils.isNotEmpty(client.getScope()) && StringUtils.isNotEmpty(request.getScope()) && !client.getScope().contains(request.getScope())){
            throw new ServiceException("错误:不支持的scope " + client.getScope() + "!");
        }
        LoginUser user = SecurityUtils.getLoginUser();
        OAuthCode code = new OAuthCode();
        code.setCode(OAuthUtil.buildCode());
        code.setUser(user);
        code.setClient(client);
        code.setRedirectUri(request.getRedirectUri() == null ? client.getRedirectUri() : request.getRedirectUri());
        code.setScope(request.getScope() != null ? request.getScope(): "public" );
        code.setToken(request.getAccessToken());
        redisCache.setCacheObject("oauth_code:"+ code.getCode(), code);
        return code;
    }

    /**
     *  code转换成token
     * @param request
     * @param tokenRequest
     * @return
     */
    public  OauthToken code2Token(HttpServletRequest request, OAuthRequest tokenRequest) {
        String code = tokenRequest.getCode();
        Object obj = redisCache.getCacheObject("oauth_code:" + code);
        redisCache.deleteObject("oauth_code:" + code);
        if(obj == null){
            throw new ServiceException("错误:code不存在或已过期!");
        }
        OAuthCode oAuthCode = (OAuthCode)obj;

        return grantToken(oAuthCode.getClient(), oAuthCode.getUser(), oAuthCode.getToken(), oAuthCode.getScope());
    }

    /**
     *  直接用客户端ID和密钥生成token
     * @param request
     * @param tokenRequest
     * @return
     */
    @Override
    public  OauthToken secret2Token(HttpServletRequest request, OAuthRequest tokenRequest){
        OauthClient client = checkClient(tokenRequest.getClientId(), null);
        if(StringUtils.isNotEmpty(client.getScope()) && tokenRequest.getScope() != null && !client.getScope().contains(tokenRequest.getScope())){
            throw new ServiceException("错误:不支持的scope " + client.getScope() + "!");
        }

        if(StringUtils.isEmpty(tokenRequest.getClientSecret())){
            throw new ServiceException("错误:密钥不能为空");
        }else if(!client.getClientSecret().equals(tokenRequest.getClientSecret())){
            throw new ServiceException("错误:密钥校验失败");
        }

        String scope = client.getScope();
        if(tokenRequest.getScope() != null){
            scope = tokenRequest.getScope();
        }
        return grantToken(client, null, OAuthUtil.buildToken(), scope);
    }
    /**
     *  刷新令牌
     * @param request
     * @return
     */
    public  OauthToken refresh2Token(HttpServletRequest request){
        OAuthRequest oAuthRequest = new OAuthRequest(request);
        OauthClient client = checkClient(oAuthRequest.getClientId(), oAuthRequest.getClientSecret());

        String refreshToken = OAuthUtil.getRefreshToken(request);
        Object obj = redisCache.getCacheObject("oauth_refresh:"+ refreshToken);
        if(obj == null){
            throw new ServiceException("错误:刷新令牌不存在或已过期!");
        }
        redisCache.deleteObject("oauth_refresh:"+ refreshToken);
        OauthToken oauthToken = (OauthToken)obj;
        if(oauthToken.getCreateTime().getTime() + 24 * 3600 * 1000 < System.currentTimeMillis()){
            throw new ServiceException("错误:刷新令牌已过期!");
        }
        oauthLoginService.logout(oauthToken);
        String token = oauthLoginService.login(oauthToken.getUser());

        return grantToken(client, oauthToken.getUser(), token, OAuthUtil.getScope(request));
    }

    /**
     *  简单模式，账号密码直接生成token
     * @param request
     * @param tokenRequest
     * @return
     */
    public  OauthToken pwd2Token(HttpServletRequest request, OAuthRequest tokenRequest){
        OauthClient client = checkClient(tokenRequest.getClientId(), tokenRequest.getClientSecret());
        if(StringUtils.isNotEmpty(client.getScope())  && tokenRequest.getScope() != null && !client.getScope().contains(tokenRequest.getScope())){
            throw new ServiceException("错误:不支持的scope " + client.getScope() + "!");
        }

        String scope = client.getScope();
        if(tokenRequest.getScope() != null){
            scope = tokenRequest.getScope();
        }

        //登录验证
        String token = oauthLoginService.login(request.getParameter(OAuthUtil.OAUTH_USERNAME), request.getParameter(OAuthUtil.OAUTH_PASSWORD));
        return grantToken(client, SecurityUtils.getLoginUser(), token, scope);
    }

    /**
     * 生成令牌
     * @param client
     * @param user
     * @return
     */
    public OauthToken grantToken(OauthClient client, LoginUser user, String accessToken, String scope){
        OauthToken oauthToken = new OauthToken();
        oauthToken.setAccessToken(accessToken);
        oauthToken.setRefreshToken(OAuthUtil.buildToken());
        oauthToken.setClientId(client.getClientId());
        oauthToken.setUser(user);
        oauthToken.setUserName(user == null ? null : user.getUsername());
        oauthToken.setScope(StringUtils.isEmpty(scope)? "public" : scope);
        oauthToken.setCreateTime(new Date());
        oauthToken.setExpiredTime(new Date(System.currentTimeMillis() + client.getAccessValidity() * 60 * 1000));

        redisCache.setCacheObject("oauth_token:" + oauthToken.getAccessToken(), oauthToken);
        redisCache.setCacheObject("oauth_refresh:" + oauthToken.getRefreshToken(), oauthToken);
        oauthTokenService.insertOauthToken(oauthToken);
        return oauthToken;
    }

    /**
     * 校验令牌
     * @param request
     * @return
     */
    public OauthToken checkToken(HttpServletRequest request){
        String accessToken = OAuthUtil.getAccessToken(request);
        if (StringUtils.isEmpty(accessToken)) {
            throw new ServiceException("错误:token不正确!");
        }

        Object obj = redisCache.getCacheObject("oauth_token:" + accessToken);
        if(obj == null){
            throw new ServiceException("错误:令牌不不存在或已过期!");
        }
        OauthToken oauthToken = (OauthToken) obj;
        if(oauthToken.getExpiredTime().getTime() < System.currentTimeMillis()){
            redisCache.deleteObject("oauth_token:" + accessToken);
            throw new ServiceException("错误:令牌已过期，请重新登录!");
        }

        return oauthToken;
    }


    /**
     * 生成登录页面参数
     * @param model
     * @param request
     * @return
     */
    @Override
    public boolean getLogin(Model model, OAuthRequest request){
        model.addAttribute("client_id", request.getClientId());
        model.addAttribute("redirect_uri", request.getRedirectUri());
        model.addAttribute("response_type", request.getResponseType());
        model.addAttribute("state", request.getState());
        model.addAttribute("scope", request.getScope() == null ? "" : request.getScope());
        return true;
    }

    /**
     * 记录日志
     * @param clientId
     * @param userName
     * @param api
     * @param ip
     * @param token
     * @param remark
     * @param state
     * @return
     */
    @Override
    public int log(String clientId, String userName, String api, String ip, String token, String remark, String state){
        OauthLog oauthLog = new OauthLog();
        oauthLog.setClientId(clientId);
        oauthLog.setApi(api);
        oauthLog.setIp(ip);
        oauthLog.setLogTime(new Date());
        oauthLog.setToken(token);
        oauthLog.setState(state);
        oauthLog.setUserName(userName);
        oauthLog.setRemark(remark == null || remark.length() < 255 ? remark: remark.substring(0,254));
        return oauthLogService.insertOauthLog(oauthLog);

    }
}
